package com.dc.project.workflow.service.impl;

import com.dc.common.exception.ServiceException;
import com.dc.common.utils.DateUtil;
import com.dc.common.utils.ShiroUtil;
import com.dc.common.utils.StringUtil;
import com.dc.project.workflow.dto.FlowData;
import com.dc.project.workflow.dto.NodeAssignee;
import com.dc.project.workflow.dto.ProcessNode;
import com.dc.project.workflow.service.ProcessEngineService;
import com.dc.project.workflow.utils.ProcessUtil;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author zhuangcy
 * @date 2021/6/29
 * @description 流程引擎服务实现类
 */
@Slf4j
@Service("processEngineService")
public class ProcessEngineServiceImpl implements ProcessEngineService {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;
    @Resource
    private HistoryService historyService;


    @Override
    public String start(String deploymentId, String businessId, String startUser) {
        // 流程流转任务变量集合
        final HashMap<String, Object> taskVariables = new HashMap<>();
        final FlowData flowData = new FlowData();

        if (StringUtil.isEmpty(deploymentId)) {
            throw new ServiceException("流程发布ID不允许为空");
        }
        if (StringUtil.isEmpty(startUser)) {
            // 获取当前登录用户
            startUser = ShiroUtil.getUserInfo().getUserCode();
        }

        flowData.setStartUser(startUser);
        flowData.setCurrentUser(startUser);
        flowData.setFirstNode(true);
        flowData.setFirstSubmit(true);
        flowData.setNextUser(startUser);
        taskVariables.put(ProcessUtil.FLOW_DATA_KEY, flowData);

        // 设置当前任务的办理人
        Authentication.setAuthenticatedUserId(startUser);

        ProcessDefinition processDefinition = ProcessUtil.getProcessDefinition(deploymentId);
        if (processDefinition.isSuspended()) {
            throw new ServiceException("该流程已被挂起，无法使用，请激活后使用");
        }
        String processDefinitionId = processDefinition.getId();

        ProcessInstance processInstance;
        if (StringUtil.isEmpty(businessId)) {
            processInstance = runtimeService.startProcessInstanceById(processDefinitionId, taskVariables);
        } else {
            // 使用业务表启动流程
            processInstance = runtimeService.startProcessInstanceById(processDefinitionId, businessId, taskVariables);
        }

        // 设置流程启动后的相关核心变量
        String processName = processDefinition.getName();
        String processInstanceId = processInstance.getProcessInstanceId();
        Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
        String taskId = task.getId();

        // 设置流程实例名称
        runtimeService.setProcessInstanceName(processInstanceId, processName);

        flowData.setProcessName(processName);
        flowData.setProcessDefinitionId(processDefinitionId);
        flowData.setProcessInstanceId(processInstanceId);
        flowData.setFirstNodeId(task.getTaskDefinitionKey());
        flowData.setCurrentNodeId(task.getTaskDefinitionKey());
        flowData.setCurrentNodeName(task.getName());
        flowData.setTaskId(taskId);
        flowData.setExecutionId(task.getExecutionId());
        // 储存流程核心流转变量
        taskVariables.put(ProcessUtil.FLOW_DATA_KEY, flowData);
        // 储存流程表单流转数据
//        taskVariables.put(ProcessUtil.FLOW_VARIABLES_KEY, new HashMap<>());
        taskService.setVariables(taskId, taskVariables);
        log.debug("用户【{}】启动流程【{}】实例【{}】", startUser, processName, processInstance.getId());
        return taskId;
    }

    @Override
    public void submitTask(Map<String, Object> currentUserTaskVariables) {
        String currentUser = ShiroUtil.getUserInfo().getUserCode();
        Map<String, Object> variables = new HashMap<>(2);
        FlowData flowData = (FlowData) currentUserTaskVariables.get(ProcessUtil.FLOW_DATA_KEY);
//        HashMap<String, Object> flowVariables = (HashMap<String, Object>) currentUserTaskVariables.get(ProcessUtil.FLOW_VARIABLES_KEY);
        // 下一节点审批人
        String nextUser = flowData.getNextUser();
        String comment = StringUtil.isEmpty(flowData.getComment()) ? ProcessUtil.DEFAULT_AGREE_COMMENT : flowData.getComment();
        String taskId = flowData.getTaskId();
        String processInstanceId = flowData.getProcessInstanceId();
        int nextNodeNum = flowData.getNextNodeNum();
        Task currentTask = ProcessUtil.getCurrentTask(taskId);
        String currentNodeId = currentTask.getTaskDefinitionKey();
        if (StringUtil.isEmpty(processInstanceId)) {
            throw new ServiceException("流程实例ID不允许为空");
        }
        if (StringUtil.isEmpty(nextUser) && nextNodeNum != 0) {
            throw new ServiceException("下一环节审批人不允许为空");
        }

        if (flowData.isFirstSubmit()) {
            flowData.setFirstSubmitTime(DateUtil.getNowDate());
        }
        flowData.setFirstNode(false);
        flowData.setFirstSubmit(false);

        // 记录每个实例任务节点审批人
        HashMap<String, NodeAssignee> nodeAssignee = flowData.getNodeAssignee();
        if (nodeAssignee == null) {
            nodeAssignee = new HashMap<>(1);
        }
        nodeAssignee.put(currentNodeId + "_" + taskId, new NodeAssignee(taskId, currentNodeId, currentUser));
        flowData.setNodeAssignee(nodeAssignee);

//        variables.put(ProcessUtil.FLOW_VARIABLES_KEY, flowVariables);
        variables.put(ProcessUtil.FLOW_DATA_KEY, flowData);

        if (nextNodeNum == 0 || nextNodeNum == 1) {
            // 添加审批意见
            taskService.addComment(taskId, processInstanceId, comment);
            // owner不为空说明可能存在委托任务
            /*if (StringUtil.isNotBlank(currentTask.getOwner())) {
                DelegationState delegationState = currentTask.getDelegationState();
                // 把被委托人代理处理后的任务交回给委托人
                if (DelegationState.PENDING == delegationState) {
                    taskService.resolveTask(currentTask.getId());
                }
            }*/

            // 设置当前任务的办理人
            Authentication.setAuthenticatedUserId(nextUser);

            // 先置空，再设置。否则提交完任务后，历史任务表不会保存审批人。可能是activiti6的bug的吧？
            taskService.setAssignee(taskId, "");
            taskService.setAssignee(taskId, currentUser);

            // 正式提交任务
            taskService.complete(taskId, variables);
        } else if (nextNodeNum > 1) {
            if (StringUtil.isEmpty(flowData.getTargetNodeId())) {
                taskService.complete(taskId, variables);
            } else {
                // 指定节点
                ProcessUtil.nodeJumpTo(taskId, flowData.getTargetNodeId(), currentUser, variables, comment);
            }
        } else {
            throw new ServiceException("提交任务异常");
        }

    }

    @Override
    public void suspendProcessInstance(String processInstanceId) {
        runtimeService.suspendProcessInstanceById(processInstanceId);
    }

    @Override
    public void activateProcessInstance(String processInstanceId) {
        runtimeService.activateProcessInstanceById(processInstanceId);
    }

    @Override
    public void suspendProcessDefinitionByIds(List<String> deploymentIds) {
        deploymentIds.forEach(deploymentId -> {
            ProcessDefinition processDefinition = ProcessUtil.getProcessDefinition(deploymentId);
            if (!processDefinition.isSuspended()) {
                repositoryService.suspendProcessDefinitionById(processDefinition.getId());
            }
        });
    }

    @Override
    public void activateProcessDefinitionByIds(List<String> deploymentIds) {
        deploymentIds.forEach(deploymentId -> {
            ProcessDefinition processDefinition = ProcessUtil.getProcessDefinition(deploymentId);
            if (processDefinition.isSuspended()) {
                repositoryService.activateProcessDefinitionById(processDefinition.getId());
            }
        });
    }

    @Override
    public void withdrawApproval(String oldTaskId) {
        final String currentUser = ShiroUtil.getUserInfo().getUserCode();
        HistoricTaskInstance historicTaskInstance = ProcessUtil.getHistoricTaskInstance(oldTaskId);
        // 目标跳转节点
        final String targetNodeId = historicTaskInstance.getTaskDefinitionKey();
        final String processDefinitionId = historicTaskInstance.getProcessDefinitionId();
        final String processInstanceId = historicTaskInstance.getProcessInstanceId();

        if (ProcessUtil.isFinished(processInstanceId)) {
            throw new ServiceException("流程已结束，不能撤回");
        }

        // 判断节点是否已被提交，如已提交则不能撤回
        List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
        if (tasks.size() == 1) {
            Task currentTask = tasks.get(0);
            String currentTaskId = currentTask.getId();
            String currentNodeId = currentTask.getTaskDefinitionKey();

            final Map<String, Object> variables = taskService.getVariables(currentTaskId);

            // 计算期望节点ID
            List<UserTask> nextNodes = ProcessUtil.getNextNode(processDefinitionId, targetNodeId, variables);
            if (nextNodes.size() > 1) {
                throw new ServiceException("撤回失败，任务已被提交");
            }
            UserTask userTask = nextNodes.get(0);
            String expectNodeId = userTask.getId();
            if (currentNodeId.equals(expectNodeId)) {
                // 撤回
                ProcessUtil.setNextUser(currentUser, variables);
                ProcessUtil.nodeJumpTo(currentTaskId, targetNodeId, variables, "撤回");
                // 删除
//                activityMapper.deleteHisTaskInstanceByTaskId(currentTaskId);
//                activityMapper.deleteHisActivityInstanceByTaskId(currentTaskId);
            } else {
                throw new ServiceException("撤回失败，任务已被提交");
            }

        } else if (tasks.size() > 1) {
            // TODO: 2020/5/1 多任务节点撤回
            throw new ServiceException("目前暂不支持多任务撤回");
        } else {
            throw new ServiceException("撤回失败，任务已被提交或不存在");
        }

    }

    @Override
    public Map<String, Object> getCurrentUserTaskVariables(String taskId) {
        final Map<String, Object> resultMap = new HashMap<>();
        final String currentUser = ShiroUtil.getUserInfo().getUserCode();
        // Task currentTask = ProcessUtil.getCurrentUserTask(taskId);
        Task currentTask = ProcessUtil.getCurrentTask(taskId);
        // 流程完结或当前用户没有权限
        if (currentTask == null) {
            return null;
        }

        // 获取当前节点表单数据
//        Map<String, Object> variables = ProcessUtil.getVariables(taskId);
        final Map<String, Object> flowVariables = runtimeService.getVariables(currentTask.getExecutionId());
//        final Map<String, Object> flowVariables = ProcessUtil.getFlowVariables(variables);
        final FlowData flowData = ProcessUtil.getFlowData(taskId);

        final String processDefinitionId = flowData.getProcessDefinitionId();
        final String currentNodeId = currentTask.getTaskDefinitionKey();
        final String currentExecutionId = currentTask.getExecutionId();
        final String currentNodeName = currentTask.getName();

        // 更新任务流转核心数据变量【FlowData】

        // 如果当前任务不是首节点
        if (!flowData.isFirstNode()) {
            flowData.setFirstNode(false);
        }
        flowData.setCurrentUser(currentUser);
        flowData.setCurrentNodeId(currentNodeId);
        flowData.setCurrentNodeName(currentNodeName);
        flowData.setTaskId(taskId);
        flowData.setExecutionId(currentExecutionId);

        resultMap.put(ProcessUtil.FLOW_DATA_KEY, flowData);
//        resultMap.putAll(flowVariables);
//        resultMap.put(ProcessUtil.FLOW_VARIABLES_KEY, flowVariables);

        // 设置下一步路由出口用户任务节点集
        List<UserTask> nextUserTask = ProcessUtil.getNextNode(processDefinitionId, currentNodeId, flowVariables);
        List<ProcessNode> processNodes = ProcessUtil.convertTo(nextUserTask);
        flowData.setNextNodes(processNodes);
        flowData.setNextNodeNum(processNodes.size());

        // 设置当前环节可驳回的所有任务节点集
        List<ProcessNode> canBackNodes = ProcessUtil.getCanBackNodes(currentNodeId, processDefinitionId);
        flowData.setCanBackNodes(canBackNodes);

        return resultMap;
    }

    @Override
    public void backToFirstNode(String taskId) {
        backToFirstNode(taskId, ProcessUtil.DEFAULT_AGREE_BACK_FIRST);
    }

    @Override
    public void backToFirstNode(String taskId, String comment) {
        final String currentUser = ShiroUtil.getUserInfo().getUserCode();
        Map<String, Object> variables = taskService.getVariables(taskId);
        FlowData flowData = ProcessUtil.getFlowData(variables);
        flowData.setFirstNode(true);
        flowData.setNextUser(flowData.getStartUser());
        ProcessUtil.nodeJumpTo(taskId, flowData.getFirstNodeId(), currentUser, variables, comment);
    }

    @Override
    public boolean backToPreNode(String taskId, String comment) {
        final String currentUser = ShiroUtil.getUserInfo().getUserCode();
        Map<String, Object> variables = taskService.getVariables(taskId);
        FlowData flowData = ProcessUtil.getFlowData(variables);
        Task currentTask = ProcessUtil.getCurrentTask(taskId);
        // 获取上一环节
        ProcessNode preOneIncomeNode = ProcessUtil.getPreOneIncomeNode(currentTask.getTaskDefinitionKey(), currentTask.getProcessDefinitionId());
        if (preOneIncomeNode == null) {
            throw new ServiceException("驳回失败，preOneIncomeNode空指针异常");
        }
        String preNodeId = preOneIncomeNode.getNodeId();
        // 如果是首环节
        boolean isFirstNode = false;
        if (preNodeId.equals(flowData.getFirstNodeId())) {
            flowData.setFirstNode(true);
            flowData.setNextUser(flowData.getStartUser());
            isFirstNode = true;
        } else {
            // 获取目标节点审批人
            int taskType = ProcessUtil.getTaskType(preNodeId, currentTask.getProcessDefinitionId());
            if (taskType == ProcessUtil.USER_TASK_TYPE_NORMAL) {
                HistoricActivityInstance historicActivityInstance = historyService.createHistoricActivityInstanceQuery()
                        .processInstanceId(currentTask.getProcessInstanceId())
                        .activityId(preNodeId)
                        .finished()
                        .listPage(0, 1).get(0);
                String assignee = historicActivityInstance.getAssignee();
                flowData.setNextUser(assignee);
            } else {
                throw new ServiceException("目前不支持多任务实例驳回");
            }
        }
        String upComment = StringUtil.isEmpty(comment) ? "驳回上环节" : comment;
        ProcessUtil.nodeJumpTo(taskId, preNodeId, currentUser, variables, upComment);
        // 返回是否为首节点
        return isFirstNode;
    }

    @Override
    public boolean backToPreNode(String taskId) {
        return backToPreNode(taskId, null);
    }

    @Override
    public boolean backToAnyNode(String taskId, String targetNodeId, String comment) {
        final String currentUser = ShiroUtil.getUserInfo().getUserCode();
        Map<String, Object> variables = taskService.getVariables(taskId);
        FlowData flowData = ProcessUtil.getFlowData(variables);
        // 如果是首环节
        boolean isFirstNode = false;
        if (targetNodeId.equals(flowData.getFirstNodeId())) {
            flowData.setFirstNode(true);
            flowData.setNextUser(flowData.getStartUser());
            isFirstNode = true;
        } else {
            // 获取目标节点审批人
            int taskType = ProcessUtil.getTaskType(targetNodeId, flowData.getProcessDefinitionId());
            if (taskType == ProcessUtil.USER_TASK_TYPE_NORMAL) {
                List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
                        .processInstanceId(flowData.getProcessInstanceId()).activityId(targetNodeId).finished().listPage(0, 1);
                HistoricActivityInstance historicActivityInstance = list.get(0);
                if (null == historicActivityInstance) {
                    throw new ServiceException("未找到历史活动实例");
                }
                String assignee = historicActivityInstance.getAssignee();
                flowData.setNextUser(assignee);
            } else {
                throw new ServiceException("目前不支持多任务实例驳回");
            }
        }
        FlowElement flowElement = ProcessUtil.getFlowElement(targetNodeId, flowData.getProcessDefinitionId());
        String remark = "驳回【" + flowElement.getName() + "】环节";
        String upComment = StringUtil.isEmpty(comment) ? remark : comment + ";" + remark;
        ProcessUtil.nodeJumpTo(taskId, targetNodeId, currentUser, variables, upComment);

        return isFirstNode;
    }

    @Override
    public boolean backToAnyNode(String taskId, String targetNodeId) {
        return backToAnyNode(taskId, targetNodeId, null);
    }
}
